package demo2

/*
  切片 -- 是对数组一个连续片段的引用, 是一个引用类型。
  切片和数组的不同:
    1. 切片是引用类型
    2. 切片没有固定长度
*/

import "fmt"

// TestDemo26 测试Slice
func TestDemo26() {
	var a = [5]int{1, 2, 3, 4, 5}

	// demo1: 新建切片
	var b = a[2:4]
	fmt.Println(b)

	// demo2: 从指定范围中生成切片
	var list [30]int

	for i := 0; i < 30; i++ {
		list[i] = i + 1
	}

	// 区间
	fmt.Println(list[10:19])

	// 中间到结尾的元素
	fmt.Println(list[24:])

	// 开始到中间的元素
	fmt.Println(list[:10])

	// 和原来的数组、切片一致
	fmt.Println(list[:])

	// 重置切片，清空拥有的元素
	c := []int{1, 3, 4, 5, 6}
	fmt.Println(a[0:0]) // []
	fmt.Println(c)      // c 保持不变

	// demo3: 直接声明新的切片
	// 声明 字符串切片
	var strList []string
	// 整数型切片
	var numList []int
	// 空切片
	var numListEmpty = []int{}

	// 输出 3 个切片
	fmt.Println(strList, numList, numListEmpty)

	// 三个切片的大小
	fmt.Println(len(strList), len(numList), len(numListEmpty))

	// 声明但未使用的切片的默认值是 nil，
	// 切片的是否为空判断
	fmt.Println(strList == nil) // true
	fmt.Println(numList == nil) // true
	// numListEmpty 已经被分配到了内存，但没有元素，因此和 nil 比较时是 false。
	fmt.Println(numListEmpty == nil) // false

	// demo4: 使用 make() 函数构造切片, 动态地创造切片
	// make( []T, size, cap )
	// T 是类型， size 是切片分配元素的数量，cap 是预分配的元素数量，不影响 size
	d := make([]int, 2)
	e := make([]int, 2, 10)

	// 使用 make() 函数生成的切片一定发生了内存分配操作。
	fmt.Println(d, e)           // [0 0]
	fmt.Println(len(d), len(e)) // [0 0]

	/*
	   demo5: 使用 append() 为切片添加元素
	   tips:
	     1. 在容量不足的情况下， append 的操作会导致重新分配内存（扩容），可能导致巨大的内存分配和复制数据代价
	     2. 即使容量足够，依然需要用 append 函数的返回值来更新切片本身，因为新切片的长度已经发生了变化。
	     3. 切片在扩容时，容量的扩展规律按容量的 2 倍数扩充
	*/
	var numbers []int

	for i := 0; i < 10; i++ {
		// 用 append 函数的返回值来更新切片本身
		numbers = append(numbers, i)
		fmt.Printf("len: %d cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
	}

	// 在切片的开头添加元素, 在开头一般都会导致内存的重新分配, 比从尾部追加元素的性能差很多。
	var f = []int{1, 2, 3, 4, 5}
	f = append([]int{0}, f...)          // 在开头添加1个元素
	f = append([]int{-3, -2, -1}, f...) // 在开头添加一个切片

	// append() 返回值的链式操作
	// 每个添加操作中的第二个 append 调用都会创建一个临时切片，
	// 并将 a[i:] 的内容复制到新创建的切片中，然后将临时创建的切片再追加到 a[:i] 。
	var g = []int{1, 2, 3, 4, 5}
	// var g []int = []int{1, 2, 3, 4, 5}
	g = append(g[:2], append([]int{55}, g[2:]...)...) // 在第 2 个元素插入 33
	fmt.Println(g)
	g = append(g[:3], append([]int{11, 22, 33}, g[3:]...)...) // 在第三个位置插入切片
	fmt.Println(g)

	fmt.Println("----append 返回值测试----")
	// tips: append 返回值是生成的新的 slice, 不修改原始的 slice
	var g1 = []int{1, 2, 3, 4, 5}
	// var g1 []int = []int{1, 2, 3, 4, 5}
	g2 := append(g1, 6)
	fmt.Println(g1) // [1, 2, 3, 4, 5]
	fmt.Println(g2) // [1, 2, 3, 4, 5, 6]
	g3 := append([]int{0}, g1...)
	fmt.Println(g1) // [1, 2, 3, 4, 5]
	fmt.Println(g3) // [0, 1, 2, 3 ,4, 5]
	g11 := []int{11, 22, 33}
	g12 := append(g11, g1...)
	fmt.Println("append--对于原始值")
	fmt.Println(g1)  // [1 2 3 4 5]
	fmt.Println(g11) // [11 22 33]
	fmt.Println(g12) // [11 22 33 1 2 3 4 5]

	/*
	   demo6: 使用 copy() 复制切片
	   copy(slice1, slice2)
	     1. copy 函数的第一个参数是要复制的目标 slice
	     2. 第二个参数是源 slice
	     3. 两个 slice 可以共享同一个底层数组，甚至有重叠也没有问题。
	     4. copy 函数将返回成功复制的元素的个数，等于两个 slice 中较小的长度，
	        所以我们不用担心覆盖会超出目标 slice 的范围。
	     5. 返回值是 复制成功的元素的个数

	*/
	fmt.Println("------------copy() 方法研究")
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := []int{5, 4, 3}

	copy(slice2, slice1)
	fmt.Println(slice1) // 不变
	fmt.Println(slice2) // 只会复制slice1的前3个元素到slice2中

	copy(slice1, slice2)
	fmt.Println(slice1) // 只会复制slice2的3个元素到slice1的前3个位置
	fmt.Println(slice2) // 不变

	// 设置元素数量为1000
	const elementCount = 1000
	// 预分配足够多的元素切片
	srcData := make([]int, elementCount)
	// 将切片复制
	for j := 0; j < elementCount; j++ {
		srcData[j] = j
	}
	// 引用切片数据, 源数据变化则引用数据也变化
	refData := srcData
	// 预分配足够多的元素切片
	copyData := make([]int, elementCount)
	// 将数据复制到新的切片空间中, 复制数据不会随着源数据的变化而变化
	copy(copyData, srcData)
	// 修改原始数据的第一个元素
	srcData[0] = 999
	// 打印引用切片的第一个元素
	fmt.Println(refData[0]) // 999
	// 打印复制切片的第一个和最后一个元素
	fmt.Println(copyData[0], copyData[elementCount-1]) // 0 999
	// 复制原始数据从 4 到 6
	copy(copyData, srcData[4:6])

	for i := 0; i < 5; i++ {
		fmt.Printf("%d\n", copyData[i]) // 4 5 2 3 4
	}

	/*
	   demo7: 从切片中删除元素:
	     根据要删除元素的位置有三种情况：从开头位置删除，从中间位置删除，从尾部删除。其中删除切片尾部的元素最快。
	     tips: 删除需要借助 子切片 来实现
	*/
	h := []int{1, 2, 3, 4, 5}
	// 删除尾部的 1 个元素, 速度最快
	h1 := h[:len(h)-1]
	h2 := h[:len(h)-2]
	fmt.Println(h1)
	fmt.Println(h2)
	// 删除开头的元素
	h3 := h[1:]
	h4 := h[2:]
	fmt.Println(h3)
	fmt.Println(h4)
	//Todo: append 删除开头元素, 存在诡异的事件
	h5 := []int{1, 2, 3, 4, 5}
	// h5 = append(h5[:0], h5[1:]...) // 删除开头 1个元素
	// h6 := append(h5[:0], h5[2:]...) // 删除开头 2个元素
	fmt.Println("----删除 slice 开头元素")
	h51 := h5[1:]
	fmt.Println(h51) // [2 3 4 5]
	h52 := append(h5[:0], h51...)
	fmt.Println(h52) // [2 3 4 5]
	fmt.Println(h5)  //
	// h53 := append(h5[:0], h51...)
	// fmt.Println(h5)
	// fmt.Println(h53)

	// 使用 copy 删除开头元素
	h7 := []int{1, 2, 3, 4, 5}
	fmt.Println("------copy 删除")
	fmt.Println(h7[1:])
	h71 := h7[:copy(h7, h7[1:])] // 删除开头 1 个元素
	fmt.Println(h7)
	fmt.Println(h71)

	// 使用 append, copy 删除中间的元素
	// tips: slice 由于是引用类型，append, copy 会影响到原来 slice 的值
	fmt.Println("-----append,copy 删除中间")
	h8 := []int{1, 2, 3, 4, 5}
	h81 := append(h8[:2], h8[3:]...)
	fmt.Println(h81)
	fmt.Println(h8)
	h82 := h8[:2+copy(h8[2:], h8[3:])]
	fmt.Println(h82)

	seq := []string{"a", "b", "c", "d", "e"}

	// 删除位置
	index := 3

	// 查看删除位置之前、之后的元素
	fmt.Println(seq[:index], seq[index:])

	// 需要使用 seq 接受 append 的返回值
	seq = append(seq[:index], seq[index:]...)

	fmt.Println(seq)

	// tips: Go 语言中切片删除元素的本质是：以被删除元素为分界点，将前后两个部分的内存重新连接起来。
	//   1. 随着元素的增加，删除过程将会变得极为耗时
	//   2. 当业务需要大量、频繁地从一个切片中删除元素时，
	//      如果对性能要求较高，就需要反思是否需要更换其他的容器（如双链表等能快速从删除点删除元素）

	/*
		demo8: range关键字：循环迭代切片
	*/
	// 创建切片，容量和长度为 4
	rangeSlice := []int{10, 20, 30, 40}
	// 迭代元素
	for index, value := range rangeSlice {
		fmt.Printf("Index: %d, Value: %d\n", index, value)
	}

	// range 创建了每个元素的副本，而不是直接返回对该元素的引用，使用该值变量的地址作为指针，会报错
	//   1. 要想获取每个元素的地址，可以使用切片变量和索引值， slice[index]
	rangeSlice2 := []int{10, 20, 30, 40}
	for index, value := range rangeSlice2 {
		fmt.Printf("Value, %d, Value-Addr: %X, ElemenAddr: %X\n",
			value, &value, &rangeSlice2[index])
	}

	// 使用空白标识符(下划线) 来忽略索引值
	rangeSlice3 := []int{10, 20, 30, 40}
	for _, value := range rangeSlice3 {
		fmt.Printf("Value: %d\n", value)
	}

	// for 循环
	rangeSlice4 := []int{10, 20, 30, 30}
	// 从第3个元素开始迭代
	for index := 2; index < len(rangeSlice4); index++ {
		fmt.Printf("Index: %d, Value: %d\n", index, rangeSlice4[index])
	}

	// 函数 len 返回切片的长度，函数 cap 返回切片的容量
}
